#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <signal.h>
#include <sstream>
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <vector>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <functional>
#include <semaphore.h>
#include <jsoncpp/json/json.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <algorithm>
#include "Until.hpp"
#include "Log.hpp"
#define SEP ": "
#define OK 200
#define NOT_FIND 404
#define WEB_ROOT "wwwroot/"
#define HOME_PAGE "index.html"
#define LINE_END "\r\n"
#define HTTP_VERSION "HTTP/1.0"
#define PAGE_404 "404.html"
using namespace std;
static string Code2Desc(int code)
{
    string path;
    switch (code)
    {
    case 200:
    {
        path = "oK";
        break;
    }
    case 404:
    {
        path = "NOT_FIND";
        break;
    }
    default:
        break;
    }
    return path;
}
static string Suffix2Desc(const string &suffix)
{
    static unordered_map<string, string> hash = {
        {".html", "text/html"},
        {".css", "text/css"},
        {".js", "application/javascript"},
        {".jpg", "application/x-jpg"},
        {".xml", "application/xml"},
    };
    if (hash.find(suffix) != hash.end())
    {
        return hash[suffix];
    }
    else
        return "text/html";
}
class HttpRequest
{
public:
    string request_line;           // 请求行
    vector<string> request_header; // 请求报头
    string blank;                  // 空行
    string request_body;           // 请求正文
    string method;
    string url;
    string version;
    unordered_map<string, string> header_kv;
    int content_length = -1;
    string path;
    string query_string;
    bool cgi = false;
    string suffix; // 表明资源类型(后缀)
};

class HttpResponse
{
public:
    string status_line;             // 状态行
    vector<string> response_header; // 响应报头 :后缀+后缀解释 长度 
    string blank = LINE_END;        // 空行
    string response_body;           // 响应正文
    int status_code = OK;           // 状态码
    int fd = -1;
    int size = 0;                   //path/带参的get的报文大小
};

class EndPoint
{
private:
    int sock;
    HttpRequest http_request;
    HttpResponse http_response;
    bool stop = false;          //标识对端异常退出的状态
    bool RecvHttpRequestLine() // 读取请求行信息
    {
        if (Until::ReadLine(sock, http_request.request_line) > 0)
        {
            http_request.request_line.resize(http_request.request_line.size() - 1);
        }
        else
        {
            stop = true;
        }
        return stop;
    }
    void ParseHttpRequestLine()
    {
        stringstream str(http_request.status_line);
        str >> http_request.method >> http_request.url >> http_request.version;
        auto &Method = http_request.method;
        transform(Method.begin(), Method.end(), Method.begin(), ::toupper); // 将method信息统一转化为大写
    }
    bool RecvHttpRequestHeader()
    {
        string line;
        while (1)
        {
            line.clear();
            if (Until::ReadLine(sock, line) <= 0)
            {
                stop = true;
                break;
            }
            if (line == "\n")
            {
                http_request.blank = line;
                break;
            }
            line.resize(line.size() - 1);
            http_request.request_header.push_back(line);
            LOG(INFO, line);
        }
        return stop;
    }
    void ParseHttpRequestHeader()
    {
        for (auto &iter : http_request.request_header)
        {
            string key;
            string value;
            if (Until::CutString(iter, key, value, SEP))
                http_request.header_kv.insert({key, value});
        }
    }
    bool IsNeedRecvHttpRequestBody()
    {
        string Method = http_request.method;
        if (Method == "POST")
        {
            string str = http_request.header_kv["Content-Length"];
            http_request.content_length = atoi(str.c_str());
            http_request.cgi = true;
            return true;
        }
        return false;
    }
    void RecvHttpRequestBody()
    {
        char ch = 0;
        int content_length = http_request.content_length;
        while (content_length)
        {
            int n = recv(sock, &ch, 1, 0);
            if (n > 0)
            {
                http_request.request_body.push_back(ch);
                content_length--;
            }
            else
                break;
        }
    }

public:
    EndPoint(int Sock)
        : sock(Sock) {}
    bool Stop()
    {
        return stop;
    }
    int processCgi()
    {
        int code = OK;
        int input[2];
        int output[2];
        if (pipe(input) < 0)
        {
            LOG(ERROR, "创建input管道失败");
            return 404;
        }
        if (pipe(output) < 0)
        {
            LOG(ERROR, "创建output管道失败");
            return 404;
        }

        auto &method = http_request.method;
        auto &query_string = http_request.query_string;
        auto &body_text = http_request.request_body;
        int &content_length = http_request.content_length;
        auto &response_body = http_response.response_body;
        pid_t pid = fork();
        if (pid == 0)
        {
            // 子进程
            auto &bin = http_request.path;
            close(input[0]);
            close(output[1]);
            // 在进程替换前对input和output的文件描述符进行重定向
            dup2(output[0], 0);
            dup2(input[1], 1);

            // 让子进程在进程切换后知道是什么方法---采用环境变量的方式传递数据
            string method_env = "METHOD=";
            method_env += method;
            putenv((char *)method_env.c_str());

            // 根据不同的method传输不同的环境变量
            if (method == "GET")
            {
                string query_string_env = "QUERY_STRING=";
                query_string_env += query_string;
                putenv((char *)query_string_env.c_str()); // GET方法通过环境变量发送get所带参数
            }
            else if (method == "POST")
            {
                string content_length_env = "CONTENT_LENGTH=";
                content_length_env += to_string(content_length);
                putenv((char *)content_length_env.c_str());     //POST方法通过环境变量发送request的请求正文的长度
            }
            else
            {
            }

            // 进程替换
            execl(bin.c_str(), bin.c_str(), nullptr);
            exit(1);
        }
        else if (pid < 0)
        {
            // 创建子进程失败
            LOG(ERROR, "创建子进程失败");
            return 404;
        }
        else
        {
            // 父进程
            close(input[1]);
            close(output[0]);

            if (method == "POST") // POST方法直接向管道发送数据
            {
                const char *start = body_text.c_str();
                int total = 0;
                int size = 0;
                // 读取的总长度total一定小于content_length，因此这里total<content_length
                while ((total < content_length) && (size = write(output[1], start + total, body_text.size() - total)) > 0)
                {
                    total += size;
                }
            }

            // 从管道中读取数据到响应报头中(数据从子进程来，而子进程又通过进程切换在处理请求的进程中向被重定向的管道-1号文件描述符发送数据)
            char ch;
            while (read(input[0], &ch, 1) > 0)
            {
                response_body.push_back(ch);
            }

            // 设置状态码检测子进程是否正常退出，以及是否报错
            int status = 0;
            waitpid(pid, &status, 0); // 等待子进程退出
            if (WIEXITED(status))
            {
                if (WEXITSTATUS(status))
                {
                    code = OK;
                }
                else
                {
                    code = 404;
                }
            }
            else
            {
                code = 404;
            }
            // 使用完后记得关闭之前开辟的文件描述符
            close(input[0]);
            close(output[1]);
        }
        return code;
    }
    int processNonCgi()
    {
        http_response.fd = open(http_request.path.c_str(), O_RDONLY);
        if (http_request.fd >= 0)
        {
            int code = http_response.status_code;
            string &status_line = http_response.status_line;
            status_line += HTTP_VERSION;
            status_line += " ";
            status_line += to_string(code);
            status_line += " ";
            status_line += Code2Desc(code);
            status_line += LINE_END;

            // 表明资源长度
            string header_line = "Content-Length: ";
            header_line += to_string(http_response.size());
            header_line += LINE_END;
            http_response.response_header.push_back(header_line); // 将资源长度信息添加到响应报头中

            string header_suffix = "Content-Type: ";
            header_suffix += http_request.suffix;
            header_suffix += " ";
            header_suffix += Suffix2Desc(http_request.suffix);
            header_suffix += LINE_END;
            http_response.response_header.push_back(header_suffix); // 将资源后缀类型信息添加到响应报头中

            return OK;
        }
        return 404;
    }
    //读取信息
    void RecvHttpRequest()
    {
        if ((!RecvHttpRequestLine()) && (!RecvHttpRequestHeader()))
        {
            ParseHttpRequestLine();
            ParseHttpRequestHeader();
            if (IsNeedRecvHttpRequestBody())
                RecvHttpRequestBody();
        }
    }
    // 处理错误信息
    void BuildHttpResponseHelper()
    {
        int code = http_response.status_code;
        http_request.cgi = false;
        string &status_line = http_response.status_line;
        status_line += HTTP_VERSION;
        status_line += " ";
        status_line += to_string(code);
        status_line += " ";
        status_line += Code2Desc(code);
        status_line += LINE_END;
        string path = WEB_ROOT;
        switch (code)
        {
        case 404:
            path += PAGE_404;
            HandlerError(path);
            break;
        default:
            break;
        }
    }
    void HandlerError(string page)
    {
        http_response.fd = open(page.c_str(), O_RDONLY);
        if (http_response.fd > 0)
        {
            string line = "Content-Type: text/html";
            line += LINE_END;
            http_response.response_header.push_back(line);
            struct st;
            stat(PAGE_404, &st);
            line = "Content-Length: ";      //响应报头存放的是网页信息的长度
            line += to_string(st.st_size);
            line += LINE_END;
            http_response.response_header.push_back(line);
        }
    }
    void BuildHttpResponse()
    {
        string Method = http_request.method;
        if (Method != "POST" && Method != "GET")
        {
            LOG(WARNING, "method error");
            http_response.status_code = NOT_FIND;
            BuildHttpResponseHelper();
            return;
        }
        if (Method == "GET")
        {
            size_t pos = http_request.url.find("?");
            if (pos != string::npos)
            {
                http_request.cgi = true;
                Until::CutString(http_request.url, http_request.path, http_request.query_string, "?");
            }
            else
            {
                http_request.path = http_request.url;
            }
            string path = WEB_ROOT;
            path += http_request.path;
            http_request.path = path;
            if (http_request.path[http_request.path.size() - 1] == '/') // 如果是根目录的情况
            {
                http_request.path += HOME_PAGE;
            }
        }
        else if (Method == "POST")
        {
            http_request.cgi = true;
            http_request.path = http_request.url;
        }

        struct stat st;
        if (stat(http_request.path.c_str(), &st) == 0)  //返回值为0表示该路径中的文件信息存在
        {
            if (S_ISDIR(st.st_mode)) // 根目录
            {
                http_request.path += "/";
                http_request.path += HOME_PAGE;
                stat(http_request.path.c_str(), &st);
            }
            else
            {
                if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH))
                {
                    http_request.cgi = true;
                    // 说明是可执行程序，进行特殊处理
                }
            }
            http_response.size = st.st_size;
        }
        else // 说明资源不存在
        {
            string info = http_request.path;
            info += "NOT FOUND";
            LOG(WARNING, info);
            http_response.status_code = NOT_FIND;
            BuildHttpResponseHelper();          //将处理错误信息的流程同处理正常信息的流程解耦
            return;
        }

        // 表明资源后缀类型
        int found = http_request.path.rfind('.');
        if (found != string::npos)
        {
            http_request.suffix = http_request.path.substr(found);
        }
        else
        {
            http_request.suffix = ".html";
        }

        // 确定资源是否需要cgi机制处理
        if (http_request.cgi == true)
        {
            http_response.status_code = http_request.processCgi();
        }
        else
        {
            http_response.status_code = http_request.processNonCgi();
        }

        // 根据错误码处理错误信息
        if (http_response.status_code != OK)
        {
            BuildHttpResponseHelper();
            return;
        }
    }
    void SendHttpResponse()
    {
        send(sock, http_response.status_line.c_str(), http_response.status_line.size(), 0);
        for (auto &iter : http_response.response_header)
        {
            send(sock, iter, iter.size(), 0);
        }
        send(sock, http_response.blank.c_str(), http_response.blank.size(), 0);
        if (http_request.cgi)
        {
            const char *start = http_response.response_body.c_str();
            int total = 0;
            int size = 0;
            while (total < http_response.response_body.size() && (size = send(sock, start + total, http_response.response_body.size() - total, 0)) > 0)
            {
                total += size;
            }
        }
        else
        {
            sendfile(sock, http_response.fd, nullptr, http_response.size());
        }

        close(http_response.fd);
    }
}; 
class CallBack
{
public:
    void operator()(int sock)
    {
        HandlerRequest(sock);
    }
     void HandlerRequest(int _sock)
    {
        int sock = *(int *)_sock;
        EndPoint *ep = new EndPoint(sock);
        ep->RecvHttpRequest();
        if (!ep->Stop())
        {
            ep->BuildHttpResponse();
            ep->SendHttpResponse();
        }
        else
        {
           LOG(WARNING,"Recv Error ,Stop Build and Send");
        }
        delete ep;  
    }
};